package org.poem.cli;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.cli.*;
import org.apache.commons.lang3.StringUtils;
import org.poem.cli.annotation.Cli;
import org.poem.cli.exception.CliException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * @author poem
 * cli命令行工具
 */
public class CliUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(CliUtils.class);
    /**
     * 默认除法运算精度
     */
    private static final int DEF_DIV_SCALE = 10;

    /**
     * 命令行和值之间的分割符号
     */
    private static final String COMMAND_OPT_SEPARATOR = " ";

    /**
     * 获取命令行
     *
     * @param entry 实体
     * @param clazz 命令行需要转换的vo
     * @param <T>   需要转化的vo
     * @return 命令行
     */
    public static <T> String getCommandLine(T entry, Class<T> clazz) {
        try {
            return getCommandLineWithException(entry, clazz);
        } catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
            LOGGER.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取命令行
     *
     * @param entry 实体
     * @param <T>   需要转化的vo
     * @return 命令行
     */
    public static <T> String getCommandLine(T entry) {
        try {
            return getCommandLineWithException(entry);
        } catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
            LOGGER.error(e.getMessage(), e);
        }
        return null;
    }


    /**
     * 参数转换为 vo
     *
     * @param args   参数
     * @param tClass 需要转化成的vo
     * @param <T>    需要转化的类型
     * @return 转化的vo
     */
    public static <T> T parseObject(String[] args, Class<T> tClass) {
        try {
            return parseObjectWithException(args, tClass);
        } catch (IntrospectionException | InstantiationException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
            LOGGER.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取命令行
     *
     * @param entry 实体
     * @param clazz 命令行需要转换的vo
     * @param <T>   需要转化的vo
     * @return 命令行
     */
    private static <T> String getCommandLineWithException(T entry, Class<T> clazz) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
        Field[] fields = clazz.getDeclaredFields();
        List<String> cliCommands = Lists.newArrayList();
        Set<String> longOptions = Sets.newHashSet();
        for (Field field : fields) {
            Cli cli = field.getAnnotation(Cli.class);
            StringBuilder command = new StringBuilder();
            PropertyDescriptor descriptor = new PropertyDescriptor((String) field.getName(), clazz);
            Method readMethod = descriptor.getReadMethod();
            Object object = readMethod.invoke(entry, new Object[0]);
            if (longOptions.contains(cli.longOpt())) {
                throw new CliException("long option " + cli.longOpt() + " has exist in config, please reset value");
            }
            longOptions.add(cli.longOpt());
            if (cli.required() && Objects.isNull(object)) {
                throw new CliException("long option " + cli.longOpt() + " is required, but value is null.");
            }
            command.append("--")
                    .append(cli.longOpt())
                    .append(COMMAND_OPT_SEPARATOR)
                    .append("\"")
                    .append(object)
                    .append("\"");
            cliCommands.add(command.toString());
        }
        return String.join(COMMAND_OPT_SEPARATOR, cliCommands);
    }

    /**
     * 获取命令行
     *
     * @param entry 实体
     * @param <T>   需要转化的vo
     * @return 命令行
     */
    private static <T> String getCommandLineWithException(T entry) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
        Class<T> clazz = (Class<T>) entry.getClass();
        return getCommandLine(entry, clazz);
    }

    /**
     * 参数转换为 vo
     *
     * @param args   参数
     * @param tClass 需要转化成的vo
     * @param <T>    需要转化的类型
     * @return 转化的vo
     * @throws IntrospectionException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws NoSuchMethodException
     * @throws InstantiationException
     */
    private static <T> T parseObjectWithException(String[] args, Class<T> tClass) throws IntrospectionException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException {
        Options options = new Options();
        CommandLineParser commandLineParser = new DefaultParser();
        T t = tClass.getDeclaredConstructor().newInstance();
        Field[] fields = tClass.getDeclaredFields();
        Map<String, Field> map = Maps.newHashMap();
        for (Field field : fields) {
            Cli cli = field.getAnnotation(Cli.class);
            PropertyDescriptor descriptor = new PropertyDescriptor(field.getName(), tClass);
            Method readMethod = descriptor.getReadMethod();
            options.addOption(Option
                    .builder()
                    .argName(cli.name())
                    .required(cli.required())
                    .hasArg(cli.hasArg())
                    .longOpt(cli.longOpt())
                    .type(readMethod.getReturnType())
                    .desc(cli.desc())
                    .build());
            map.put(cli.longOpt(), field);
        }
        try {
            CommandLine commandLine = commandLineParser.parse(options, args);
            Option[] opts = commandLine.getOptions();
            for (Option opt : opts) {
                String name = opt.getLongOpt();
                Field field = map.get(name);
                Object v = commandLine.getOptionValue(name);
                if (opt.isRequired() && Objects.isNull(v)) {
                    throw new CliException("option " + name + " can\'t be Null .");
                }
                PropertyDescriptor descriptor = new PropertyDescriptor(field.getName(), tClass);
                Method writeMethod = descriptor.getWriteMethod();
                Method readMethod = descriptor.getReadMethod();
                if (readMethod.getReturnType().toString().contains("java.lang")) {
                    //java lang
                    Class<?>[] parameterTypes = writeMethod.getParameterTypes();
                    Object[] parameterValues = new Object[parameterTypes.length];
                    int i = 0;
                    for (Class<?> parameterType : parameterTypes) {
                        String typeName = parameterType.getName();
                        if (Objects.isNull(v) || StringUtils.isBlank(String.valueOf(v))) {
                            v = null;
                        } else if ("java.lang.Integer".equals(typeName) && StringUtils.isNotEmpty(String.valueOf(v))) {
                            parameterValues[i++] = !"null".equals(String.valueOf(v)) ? Integer.valueOf(String.valueOf(v)) : null;
                        } else if ("java.lang.Boolean".equals(typeName) && StringUtils.isNotEmpty(String.valueOf(v))) {
                            parameterValues[i++] = !"null".equals(String.valueOf(v)) ? Boolean.valueOf(String.valueOf(v)) : null;
                        } else if ("java.lang.Long".equals(typeName) && StringUtils.isNotEmpty(String.valueOf(v))) {
                            parameterValues[i++] = !"null".equals(String.valueOf(v)) ? Long.valueOf(String.valueOf(v)) : null;
                        } else if ("java.lang.Double".equals(typeName) && StringUtils.isNotEmpty(String.valueOf(v))) {
                            parameterValues[i++] = !"null".equals(String.valueOf(v)) ? new BigDecimal(String.valueOf(v)).setScale(DEF_DIV_SCALE, RoundingMode.HALF_UP).doubleValue() : null;
                        } else if ("java.lang.Float".equals(typeName) && StringUtils.isNotEmpty(String.valueOf(v))) {
                            parameterValues[i++] = !"null".equals(String.valueOf(v)) ? new BigDecimal(String.valueOf(v)).setScale(DEF_DIV_SCALE, RoundingMode.HALF_UP).floatValue() : null;
                        } else if ("java.lang.String".equals(typeName) && StringUtils.isNotEmpty(String.valueOf(v))) {
                            parameterValues[i++] = !"null".equals(String.valueOf(v)) ? String.valueOf(v) : null;
                        } else {
                            parameterValues[i++] = v;
                        }
                    }
                    writeMethod.invoke(t, parameterValues);
                }
            }
        } catch (ParseException e) {
            e.printStackTrace();
            LOGGER.error(e.getMessage(), e);
        }
        return t;
    }
}
